/**
 * @project: parallel-task
 * @package: com.ngplat.paralleltask.task
 * @filename: TaskPool.java
 *
 * Copyright (c) 2018 eSunny Info. Tech Ltd. All rights reserved.
 * 
 */
package com.ngplat.paralleltask.task;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ngplat.paralleltask.common.Cleanable;
import com.ngplat.paralleltask.constants.TaskConsts;
import com.ngplat.paralleltask.constants.TaskState;
import com.ngplat.paralleltask.exception.TaskExecutionException;
import com.ngplat.paralleltask.model.JobInfo;
import com.ngplat.paralleltask.model.TaskInfo;

/** 
 * @typename: TaskPool
 * @brief: 任务池
 * @author: KI ZCQ
 * @date: 2018年5月14日 上午11:10:20
 * @version: 1.0.0
 * @since
 * 
 */
public class TaskPool implements Cleanable {
	
	private static final Logger logger = LoggerFactory.getLogger(TaskPool.class);
	
	// 保存任务信息
	private Map<String, WorkerTask> taskCache;
	// 根任务(树形结构)
	private WorkerTask rootWorkTask;
	
	/**
	 * 创建一个新的实例 TaskPool.
	 */
	public TaskPool() {
		this(JobInfo.DEFAULT_GROUP_ID);
	}
	
	/**
	 * 创建一个新的实例 TaskPool.
	 */
	public TaskPool(String jobId) {
		taskCache = new HashMap<String, WorkerTask>();
		initRootTask(jobId);
	}
	
	/**
	 * @Description: 根任务节点
	 */
	private void initRootTask(String jobId) {
		// 任务Id
		String taskId = TaskConsts.ROOT_TASK_ID;
		
		// 生成根任务基本信息
		TaskInfo rootTask = new TaskInfo(taskId, "根任务节点");
		// 设置jobId
		rootTask.setJobId(jobId);
		// 无父节点
		rootTask.setParentIds(new String[]{});
		// 默认完成状态, 以便子任务可以执行
		rootTask.setState(TaskState.TASK_INITIAL);
		// 包装任务
		rootWorkTask = WorkerTask.wrapTask(rootTask, new TaskProcessor() {
			private static final long serialVersionUID = 1L;

			@Override
			public void runWorker(TaskContext context) throws TaskExecutionException {
				logger.info("根任务执行, 任务Id: {}, JobId: {}", taskId, jobId);
			}
		});
		taskCache.put(taskId, rootWorkTask);
	}
	
	/**
	 * @Description: 添加任务基本信息  
	 * @param taskId
	 * @param task
	 */
	public void addTask(TaskInfo taskInfo, TaskProcessor processor) {
		
		WorkerTask curTask = null;
		// 已包含该任务, 仅更新taskInfo和processor, 这样父子任务就无需互等
		if (taskCache != null && taskCache.containsKey(taskInfo.getTaskId())) {
			curTask = taskCache.get(taskInfo.getTaskId());
			curTask.setInfo(taskInfo);
			curTask.setProcessor(processor);
		} else {
			curTask = WorkerTask.wrapTask(taskInfo, processor);
		}
		
		// 添加至所有依赖父任务
		addParentTask(curTask, taskInfo.getParentIds());
		// 保存当前任务
		taskCache.put(taskInfo.getTaskId(), curTask);
	}
	
	/**
	 * @Description: 将当前任务添加至父任务的子节点中 
	 * @param task
	 * @param parentIds
	 */
	private void addParentTask(WorkerTask task, String[] parentIds) {
		try {
			// 如果为空, 则默认依赖根任务
			if(ArrayUtils.isEmpty(parentIds)) {
				WorkerTask rootTask = taskCache.get(TaskConsts.ROOT_TASK_ID);
				rootTask.addChildTask(task);
			} else {
				for(int i = 0; i < parentIds.length; i++) {
					String parentId = parentIds[i];
					
					// 先补充父任务
					if(!taskCache.containsKey(parentId)) {
						taskCache.put(parentId, new WorkerTask());	
					}
					// 依赖任务
					WorkerTask parentTask = taskCache.get(parentId);
					// 将当前服务添加至父任务的节点中
					parentTask.addChildTask(task);
				}
			}
		} catch (InterruptedException e) {
			logger.error("[添加任务信息出错, 请检查.]任务信息: {}" + task.toString(), e);
		}
	}
	
	/**
	 * @Description: 通知子任务, 父任务已完成
	 * @param taskKey	父任务Id
	 * @param state		父任务完成状态
	 */
	public void fireEvent(final String taskKey, final TaskState state) {
		// 任务
		WorkerTask parentTask = taskCache.get(taskKey);
		// 通知
		parentTask.notifyChildTask(state);
	}

	/** 
	 * @see com.ngplat.paralleltask.common.Cleanable#cleanResource()
	 */
	@Override
	public void cleanResource() {
		// 根任务
		rootWorkTask.cleanResource();
		// 其他任务
		if (taskCache != null && taskCache.size() > 0) {
			for(WorkerTask task : taskCache.values()) {
				task.cleanResource();
			}
		}
	}

	/**
	 * rootWorkTask
	 *
	 * @return  the rootWorkTask
	 * @since   1.0.0
	*/
	public WorkerTask getRootWorkTask() {
		return rootWorkTask;
	}
	
}
